/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <dlfcn.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <algorithm>
#include "dal/domain_participant.h"
#include "dal/topic.h"
#include "dal/publisher.h"
#include "dal/subscriber.h"
#include "dal/service.h"
#include "dal/client.h"
#include "dal/identifier.h"

inline bool icasecompare(const std::string& a, const std::string& b)
{
    if (a.length() == b.length()) {
        return std::equal(a.begin(), a.end(), b.begin(), [](char ch_a, char ch_b) {
            return tolower(ch_a) == tolower(ch_b);
        });
    } else {
        return false;
    }
}

inline std::string get_plugin_path()
{
    // get dds implementation
    std::string ddsImpl = "fastdds";
    const char* env = getenv("DAL_IMPLEMENTATION");
    if (env == nullptr || std::string(env).empty()) {
        ddsImpl = "fastdds";
        setenv("DAL_IMPLEMENTATION", "fastdds", 1);
    } else {
        for (int i = 0; i < sizeof(dal_dds_impls) / sizeof(dal_dds_impls[0]); i++) {
            if (icasecompare(env, dal_dds_impls[i])) {
                ddsImpl = dal_dds_impls[i];
                std::transform(ddsImpl.begin(), ddsImpl.end(), ddsImpl.begin(), tolower);
                setenv("DAL_IMPLEMENTATION", ddsImpl.c_str(), 1);
                break;
            }
        }
    }

    // search dal impl lib in current dir.

#if defined(__x86_64__)
    std::string ddsImplPath = "/opt/cyber/usr/lib/libdal_" + ddsImpl + ".so";
#elif defined(__android_aarch64__)
    std::string ddsImplPath = "/vendor/lib64/libdal_" + ddsImpl + ".so";
#else
    std::string ddsImplPath = "./libdal_" + ddsImpl + ".so";
#endif
    if (access(ddsImplPath.c_str(), F_OK) != -1) {
        std::cout << "[dal dds impl source]: " << ddsImplPath << std::endl;
        return ddsImplPath;
    }

// search dal impl lib in /usr/lib.
#if defined(__x86_64__)
    ddsImplPath = "/opt/cyber/usr/lib/libdal_" + ddsImpl + ".so";
#elif defined(__android_aarch64__)
    ddsImplPath = "/vendor/lib64/libdal_" + ddsImpl + ".so";
#else
    ddsImplPath = "/usr/lib/libdal_" + ddsImpl + ".so";
#endif
    if (access(ddsImplPath.c_str(), F_OK) != -1) {
        std::cout << "[dal dds impl source]: " << ddsImplPath << std::endl;
        return ddsImplPath;
    }

    std::cerr << "ERROR: dal impl plugin path is invalid." << std::endl;
    exit(1);
}

static struct PluginHandle
{
    void* handle_;
    PluginHandle()
    {
        handle_ = dlopen(get_plugin_path().c_str(), RTLD_NOW);
        if (handle_ == nullptr) {
            std::cerr << "ERROR: Unable to open plugin: " << dlerror() << std::endl;
            exit(1);
        }
    }

    ~PluginHandle()
    {
        dlclose(handle_);
    }
} plugin_handle;

#define COMM_FUNCTION_ARG_0(func, return_type)                                                     \
    return_type func()                                                                             \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)()>(func_handle)();                                 \
    }

#define COMM_FUNCTION_ARG_1(func, type, return_type)                                               \
    return_type func(type arg)                                                                     \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type)>(func_handle)(arg);                          \
    }

#define COMM_FUNCTION_ARG_2(func, type1, type2, return_type)                                       \
    return_type func(type1 arg1, type2 arg2)                                                       \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type1, type2)>(func_handle)(arg1, arg2);           \
    }

#define COMM_FUNCTION_ARG_3(func, type1, type2, type3, return_type)                                \
    return_type func(type1 arg1, type2 arg2, type3 arg3)                                           \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type1, type2, type3)>(func_handle)(                \
            arg1, arg2, arg3);                                                                     \
    }

#define COMM_FUNCTION_ARG_4(func, type1, type2, type3, type4, return_type)                         \
    return_type func(type1 arg1, type2 arg2, type3 arg3, type4 arg4)                               \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type1, type2, type3, type4)>(func_handle)(         \
            arg1, arg2, arg3, arg4);                                                               \
    }

#define COMM_FUNCTION_ARG_5(func, type1, type2, type3, type4, type5, return_type)                  \
    return_type func(type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5)                   \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type1, type2, type3, type4, type5)>(func_handle)(  \
            arg1, arg2, arg3, arg4, arg5);                                                         \
    }

#define COMM_FUNCTION_ARG_6(func, type1, type2, type3, type4, type5, type6, return_type)           \
    return_type func(type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5, type6 arg6)       \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type1, type2, type3, type4, type5, type6)>(        \
            func_handle)(arg1, arg2, arg3, arg4, arg5, arg6);                                      \
    }

#define COMM_FUNCTION_ARG_7(func, type1, type2, type3, type4, type5, type6, type7, return_type)    \
    return_type func(                                                                              \
        type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5, type6 arg6, type7 arg7)        \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(type1, type2, type3, type4, type5, type6, type7)>( \
            func_handle)(arg1, arg2, arg3, arg4, arg5, arg6, arg7);                                \
    }

#define COMM_FUNCTION_ARG_8(                                                                       \
    func, type1, type2, type3, type4, type5, type6, type7, type8, return_type)                     \
    return_type func(type1 arg1,                                                                   \
                     type2 arg2,                                                                   \
                     type3 arg3,                                                                   \
                     type4 arg4,                                                                   \
                     type5 arg5,                                                                   \
                     type6 arg6,                                                                   \
                     type7 arg7,                                                                   \
                     type8 arg8)                                                                   \
    {                                                                                              \
        static void* func_handle = dlsym(plugin_handle.handle_, #func);                            \
        if (func_handle == nullptr) {                                                              \
            std::cerr << "ERROR: Unable to find symbol '" << #func << "': " << dlerror()           \
                      << std::endl;                                                                \
            exit(1);                                                                               \
        }                                                                                          \
        return reinterpret_cast<return_type (*)(                                                   \
            type1, type2, type3, type4, type5, type6, type7, type8)>(func_handle)(                 \
            arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);                                       \
    }

#ifdef __cplusplus
extern "C" {
#endif

// for test
COMM_FUNCTION_ARG_0(dal_1, void)
COMM_FUNCTION_ARG_1(dal_2, int, int)
COMM_FUNCTION_ARG_1(dal_3, struct dal_ds*, void)
COMM_FUNCTION_ARG_2(dal_4, int, int, void)

// API definition
COMM_FUNCTION_ARG_4(dal_create_domain_participant,
                    dal_domain_id,
                    dal_domain_participant_qos_t*,
                    dal_domain_participant_listener_t*,
                    dal_status_mask_t,
                    dal_domain_participant_t*)

COMM_FUNCTION_ARG_5(dal_create_domain_participant_with_profile,
                    dal_domain_id,
                    dal_domain_participant_qos_t*,
                    const char*,
                    dal_domain_participant_listener_t*,
                    dal_status_mask_t,
                    dal_domain_participant_t*)

COMM_FUNCTION_ARG_1(dal_delete_domain_participant, dal_domain_participant_t*, dal_ret_t)

COMM_FUNCTION_ARG_6(dal_domain_participant_create_topic,
                    dal_domain_participant_t*,
                    const char*,
                    const char*,
                    dal_topic_qos_t*,
                    dal_topic_listener_t*,
                    dal_status_mask_t,
                    dal_topic_t*)

COMM_FUNCTION_ARG_7(dal_domain_participant_create_topic_with_profile,
                    dal_domain_participant_t*,
                    const char*,
                    const char*,
                    dal_topic_qos_t*,
                    const char*,
                    dal_topic_listener_t*,
                    dal_status_mask_t,
                    dal_topic_t*)

COMM_FUNCTION_ARG_2(dal_domain_participant_delete_topic,
                    dal_domain_participant_t*,
                    dal_topic_t*,
                    dal_ret_t)

COMM_FUNCTION_ARG_4(dal_domain_participant_create_publisher,
                    dal_domain_participant_t*,
                    dal_publisher_qos_t*,
                    dal_publisher_listener_t*,
                    dal_status_mask_t,
                    dal_publisher_t*)

COMM_FUNCTION_ARG_5(dal_domain_participant_create_publisher_with_profile,
                    dal_domain_participant_t*,
                    dal_publisher_qos_t*,
                    const char*,
                    dal_publisher_listener_t*,
                    dal_status_mask_t,
                    dal_publisher_t*)

COMM_FUNCTION_ARG_2(dal_domain_participant_delete_publisher,
                    dal_domain_participant_t*,
                    dal_publisher_t*,
                    dal_ret_t)

COMM_FUNCTION_ARG_4(dal_domain_participant_create_subscriber,
                    dal_domain_participant_t*,
                    dal_subscriber_qos_t*,
                    dal_subscriber_listener_t*,
                    dal_status_mask_t,
                    dal_subscriber_t*)

COMM_FUNCTION_ARG_5(dal_domain_participant_create_subscriber_with_profile,
                    dal_domain_participant_t*,
                    dal_subscriber_qos_t*,
                    const char*,
                    dal_subscriber_listener_t*,
                    dal_status_mask_t,
                    dal_subscriber_t*)

COMM_FUNCTION_ARG_2(dal_domain_participant_delete_subscriber,
                    dal_domain_participant_t*,
                    dal_subscriber_t*,
                    dal_ret_t)

COMM_FUNCTION_ARG_5(dal_publisher_create_datawriter,
                    dal_publisher_t*,
                    dal_topic_t*,
                    dal_datawriter_qos_t*,
                    dal_datawriter_listener_t*,
                    dal_status_mask_t,
                    dal_datawriter_t*)

COMM_FUNCTION_ARG_6(dal_publisher_create_datawriter_with_profile,
                    dal_publisher_t*,
                    dal_topic_t*,
                    dal_datawriter_qos_t*,
                    const char*,
                    dal_datawriter_listener_t*,
                    dal_status_mask_t,
                    dal_datawriter_t*)

COMM_FUNCTION_ARG_2(dal_publisher_delete_datawriter, dal_publisher_t*, dal_datawriter_t*, dal_ret_t)

COMM_FUNCTION_ARG_5(dal_subscriber_create_datareader,
                    dal_subscriber_t*,
                    dal_topic_description_t*,
                    dal_datareader_qos_t*,
                    dal_datareader_listener_t*,
                    dal_status_mask_t,
                    dal_datareader_t*)

COMM_FUNCTION_ARG_6(dal_subscriber_create_datareader_with_profile,
                    dal_subscriber_t*,
                    dal_topic_description_t*,
                    dal_datareader_qos_t*,
                    const char*,
                    dal_datareader_listener_t*,
                    dal_status_mask_t,
                    dal_datareader_t*)

COMM_FUNCTION_ARG_2(dal_subscriber_delete_datareader,
                    dal_subscriber_t*,
                    dal_datareader_t*,
                    dal_ret_t)

COMM_FUNCTION_ARG_3(dal_domain_participant_listener_set_participant_monitor_callback,
                    dal_domain_participant_listener_t*,
                    dal_participant_monitor_callback,
                    void*,
                    dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_domain_participant_listener,
                    dal_domain_participant_listener_callbacks_t,
                    dal_domain_participant_listener_t*)

COMM_FUNCTION_ARG_1(dal_delete_domain_participant_listener,
                    dal_domain_participant_listener_t*,
                    dal_ret_t)

COMM_FUNCTION_ARG_3(
    dal_datareader_take_next_sample, dal_datareader_t*, dal_data, dal_sample_info_t*, dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_datareader_listener,
                    dal_datareader_listener_callbacks_t,
                    dal_datareader_listener_t*)

COMM_FUNCTION_ARG_1(dal_delete_datareader_listener, dal_datareader_listener_t*, dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_subscriber_listener,
                    dal_subscriber_listener_callbacks_t,
                    dal_subscriber_listener_t*)

COMM_FUNCTION_ARG_1(dal_delete_subscriber_listener, dal_subscriber_listener_t*, dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_publisher_listener,
                    dal_publisher_listener_callbacks_t,
                    dal_publisher_listener_t*)

COMM_FUNCTION_ARG_1(dal_delete_publisher_listener, dal_publisher_listener_t*, dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_topic_listener,
                    dal_topic_listener_callbacks_t,
                    dal_topic_listener_t*)

COMM_FUNCTION_ARG_1(dal_delete_topic_listener, dal_topic_listener_t*, dal_ret_t)

COMM_FUNCTION_ARG_3(
    dal_datawriter_write, dal_datawriter_t*, dal_data, dal_instance_handle_t, dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_type_support, void*, dal_type_support_t*)

COMM_FUNCTION_ARG_0(dal_get_identifier, const char*)

COMM_FUNCTION_ARG_2(dal_load_config_from_path, const char*, void*, dal_ret_t)

COMM_FUNCTION_ARG_3(dal_type_support_register_type,
                    dal_type_support_t*,
                    dal_domain_participant_t*,
                    const char*,
                    dal_ret_t)

COMM_FUNCTION_ARG_1(dal_create_datawriter_listener,
                    dal_datawriter_listener_callbacks_t,
                    dal_datawriter_listener_t*)

COMM_FUNCTION_ARG_1(dal_delete_datawriter_listener, dal_datawriter_listener_t*, dal_ret_t)

COMM_FUNCTION_ARG_3(
    dal_datawriter_write_with_params, dal_datawriter_t*, dal_data, dal_write_params_t*, dal_ret_t)

COMM_FUNCTION_ARG_1(dal_type_support_get_type_name, dal_type_support_t*, const char*)
COMM_FUNCTION_ARG_1(dal_delete_type_support, dal_type_support_t*, dal_ret_t)
COMM_FUNCTION_ARG_1(dal_domain_participant_delete_contained_entities,
                    dal_domain_participant_t*,
                    dal_ret_t)
COMM_FUNCTION_ARG_1(dal_datawriter_get_guid, dal_datawriter_t*, dal_guid_t)
COMM_FUNCTION_ARG_6(dal_domain_participant_create_service,
                    dal_domain_participant_t*,
                    const char*,
                    const char*,
                    const char*,
                    dal_datareader_qos_t*,
                    dal_datawriter_qos_t*,
                    dal_service_t*)
COMM_FUNCTION_ARG_8(dal_domain_participant_create_service_with_profile,
                    dal_domain_participant_t*,
                    const char*,
                    const char*,
                    const char*,
                    dal_datareader_qos_t*,
                    dal_datawriter_qos_t*,
                    const char*,
                    const char*,
                    dal_service_t*)
COMM_FUNCTION_ARG_2(dal_domain_participant_delete_service,
                    dal_domain_participant_t*,
                    dal_service_t*,
                    dal_ret_t)
COMM_FUNCTION_ARG_4(
    dal_service_take_request, dal_service_t*, dal_service_info_t*, void*, bool*, dal_ret_t)
COMM_FUNCTION_ARG_3(dal_service_send_response, dal_service_t*, dal_request_id_t*, void*, dal_ret_t)

COMM_FUNCTION_ARG_6(dal_domain_participant_create_client,
                    dal_domain_participant_t*,
                    const char*,
                    const char*,
                    const char*,
                    dal_datawriter_qos_t*,
                    dal_datareader_qos_t*,
                    dal_client_t*)
COMM_FUNCTION_ARG_8(dal_domain_participant_create_client_with_profile,
                    dal_domain_participant_t*,
                    const char*,
                    const char*,
                    const char*,
                    dal_datawriter_qos_t*,
                    dal_datareader_qos_t*,
                    const char*,
                    const char*,
                    dal_client_t*)
COMM_FUNCTION_ARG_2(dal_domain_participant_delete_client,
                    dal_domain_participant_t*,
                    dal_client_t*,
                    dal_ret_t)
COMM_FUNCTION_ARG_2(dal_client_send_request, dal_client_t*, void*, dal_ret_t)
COMM_FUNCTION_ARG_4(
    dal_client_take_response, dal_client_t*, dal_service_info_t*, void*, bool*, dal_ret_t)
COMM_FUNCTION_ARG_3(
    dal_client_set_on_response_callback, dal_client_t*, dal_client_on_response_t, const void*, void)
COMM_FUNCTION_ARG_3(dal_service_set_on_request_callback,
                    dal_service_t*,
                    dal_service_on_request_t,
                    const void*,
                    void)
COMM_FUNCTION_ARG_2(dal_client_check_server_is_available, dal_client_t*, bool*, dal_ret_t)
#ifdef __cplusplus
}
#endif